﻿using System;
using System.IO;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

namespace Hont.HyperWigglePackage.Editor
{
    public class HyperWiggleEditorCore : EditorWindow
    {
        HyperWiggleEditorView mView;
        public HyperWiggleEditorView View
        {
            get
            {
                if (mView == null)
                {
                    mView = new HyperWiggleEditorView();
                    mView.Init(this);

                    InitCore();
                }

                return mView;
            }
        }

        IHyperWigglePlugin[] mPluginsArray;
        Mesh mPreviewModel;
        Material mCubemapMaterial;
        PRPair[] mFrameData;

        double? mCacheTime;
        string mLastPath;
        int mCurrentFrame;
        int mEndFrame;
        bool mIsPlaying;
        float mFPS;
        bool mIsLoopPreview;
        bool mIsStopCameraControl;

        public float FPS { get { return mFPS; } }
        public PRPair[] FrameData { get { return mFrameData; } }
        public int CurrentFrame { get { return mCurrentFrame; } }
        public int EndFrame { get { return mEndFrame; } }
        public bool IsPlaying { get { return mIsPlaying; } }
        public bool IsLoopPreview { get { return mIsLoopPreview; } }
        public bool IsStopCameraControl { get { return mIsStopCameraControl; } set { mIsStopCameraControl = value; } }
        public event Action OnDestroyCB;


        void InitCore()
        {
            mFPS = 30;
            mLastPath = "";
            mEndFrame = 30;
            mCurrentFrame = 0;
            mIsLoopPreview = true;
            mFrameData = new PRPair[mEndFrame];
            mPreviewModel = EditorAssetsCache.PreviewCube;
            mCubemapMaterial = EditorAssetsCache.cubemap01Material;
            mPluginsArray = HyperWiggleUtil.GetAssemblyInterfacesAndCreate<IHyperWigglePlugin>(this)
                .Where(m => m != null)
                .ToArray();

            UpdateCommon();
            UpdatePreview();
            UpdateTimeline();
        }

        [MenuItem("Tools/Hont/Utility/Hyper Wiggle")]
        static void ExecuteFunc()
        {
            GetWindow<HyperWiggleEditorCore>(true);
        }

        void Update()
        {
            UpdatePlaying();

            Repaint();
        }

        void OnGUI()
        {
            View.Refresh();
        }

        void OnDestroy()
        {
            mView.Dispose();

            if (OnDestroyCB != null)
                OnDestroyCB();
        }

        void UpdatePlaying()
        {
            if (!mIsPlaying) return;

            var currentTime = EditorApplication.timeSinceStartup;

            if (mCacheTime == null)
            {
                mCacheTime = currentTime;
            }

            var fpsPerSec = 1f / mFPS;
            var gainFrame = Mathf.FloorToInt((float)(currentTime - mCacheTime) / fpsPerSec);

            if (gainFrame > 0)
                mCacheTime = null;

            mCurrentFrame += gainFrame;

            if (mIsLoopPreview)
                mCurrentFrame = mCurrentFrame >= mEndFrame ? 0 : mCurrentFrame < 0 ? mEndFrame - 1 : mCurrentFrame;
            else
                mCurrentFrame = Mathf.Clamp(mCurrentFrame, 0, mEndFrame - 1);

            UpdateTimeline();
            UpdatePreview();
        }

        public void UpdateCommon()
        {
            View.UpdateCommon(new CommonVO()
            {
                OnLoopPreviewToggleChangedCB = OnLoopPreviewToggleChanged,
                LoopPreview = mIsLoopPreview,
                OnNewBtnClickedCB = OnNewBtnClicked,
                OnSaveBtnClickedCB = OnSaveBtnClicked,
                OnLoadBtnClickedCB = OnLoadBtnClicked,
                OnQuickLoadBtnClickedCB = OnQuickLoadBtnClicked,
                CustomButtonsArray = mPluginsArray.Select(m => m.Name).ToArray(),
                OnCustomButtonClickedCB = OnCustomButtonClicked,
            });
        }

        public void UpdatePreview()
        {
            var invalidPosition = Vector3.zero;
            var invalidRotation = Vector3.zero;
            var isInvalidWiggle = HyperWiggleUtil.IsInvalidWiggle(mFrameData, out invalidPosition, out invalidRotation);

            View.UpdatePreview(new PreviewVO()
            {
                PreviewModel = mPreviewModel,
                CubemapMaterial = mCubemapMaterial,
                IsInvalidWiggle = isInvalidWiggle,
                InvalidPositon = invalidPosition,
                InvalidRotation = invalidRotation,
                IsPlaying = mIsPlaying,
                FPS = mFPS,
                OnChangedFPSCB = OnChangedFPS,
                OnChangedValueCB = OnChangedValue,
                OnPreviewCameraTransformUpdateCB = OnPreviewCameraTransformUpdate,
                OnPreviewCameraWiggleUpdateCB = OnPreviewCameraWiggleUpdate,
                OnPlayBtnClickedCB = OnPlayBtnClicked,
                OnToBeginFrameBtnClickedCB = OnToBeginFrameBtnClicked,
                OnToEndFrameBtnClickedCB = OnToEndFrameBtnClicked,
                OnToNextFrameBtnClickedCB = OnToNextFrameBtnClicked,
                OnToPreviousFrameBtnClickedCB = OnToPreviousFrameBtnClicked,
                OtherModelsArray = new string[] { "Cube", "Capsule" },
                OtherSkyboxsArray = new string[] { "CubemapMat01", "CubemapMat02", "CubemapMat03" },
                OnChangeCubemapCB = OnChangeCubemap,
                OnChangePreviewModelCB = OnChangePreviewModel,
            });
        }

        public void UpdateTimeline()
        {
            var tracks = View.TracksArray;
            var colors = View.FrameColorsArray;

            UpdateColorsArray(mFrameData, ref colors);

            tracks = new TrackVO[]
            {
                    new TrackVO(){Name="position.x", Value = mFrameData.Select(m=> m.RelativePosition.x).ToArray()},
                    new TrackVO(){Name="position.y", Value = mFrameData.Select(m=> m.RelativePosition.y).ToArray()},
                    new TrackVO(){Name="position.z", Value = mFrameData.Select(m=> m.RelativePosition.z).ToArray()},

                    new TrackVO(){Name="rotation.x", Value = mFrameData.Select(m=> m.RelativeRotation.x).ToArray()},
                    new TrackVO(){Name="rotation.y", Value = mFrameData.Select(m=> m.RelativeRotation.y).ToArray()},
                    new TrackVO(){Name="rotation.z", Value = mFrameData.Select(m=> m.RelativeRotation.z).ToArray()},
            };

            View.UpdateTimeline(new TimelineVO()
            {
                EndFrame = mEndFrame,
                CurrentFrame = mCurrentFrame,
                OnChangeFrameCB = OnChangeFrame,
                OnChangeEndFrameCB = OnChangeEndFrame,
                TracksArray = tracks,
                ColorsArray = colors,
            });
        }

        void Load(string xml)
        {
            var po = HyperWiggleUtil.XmlDeserialize<HyperWigglePO>(xml);

            HyperWiggleUtil.ConvertPRPair(po.Data);

            mFrameData = po.Data;
            mFPS = po.FPS;
            mEndFrame = po.EndFrame;

            mView.ResetCameraBeginInfo();
            UpdatePreview();
            UpdateCommon();
            UpdateTimeline();
        }

        void UpdateColorsArray(PRPair[] source, ref Color[] colors)
        {
            if (colors == null || colors.Length != mEndFrame)
            {
                colors = new Color[mEndFrame];
            }

            for (int i = 0; i < mEndFrame; i++)
            {
                var hasValue = source[i].RelativePosition != Vector3.zero || source[i].RelativeRotation != Vector3.zero;

                colors[i] = hasValue ? GUI.color : Color.gray;
            }
        }

        void OnChangeCubemap(string skybox)
        {
            var materials = new Material[] {
                EditorAssetsCache.cubemap01Material,
                EditorAssetsCache.cubemap02Material,
                EditorAssetsCache.cubemap03Material,
            };

            mCubemapMaterial = materials.FirstOrDefault(m => m.name == skybox);

            UpdatePreview();
        }

        void OnChangePreviewModel(string model)
        {
            var models = new Mesh[] { EditorAssetsCache.PreviewCube, EditorAssetsCache.PreviewCapsule };

            mPreviewModel = models.FirstOrDefault(m => m.name == model);

            UpdatePreview();
        }

        void OnPreviewCameraTransformUpdate(Camera camera, Vector3 position, Quaternion rotation)
        {
            if (mIsStopCameraControl) return;

            camera.transform.position = position;
            camera.transform.rotation = rotation;
        }

        void OnPreviewCameraWiggleUpdate(Camera camera)
        {
            if (mIsStopCameraControl) return;

            var prPair = new PRPair();

            for (int i = 0; i <= mCurrentFrame; i++)
            {
                prPair.RelativePosition += mFrameData[i].RelativePosition;
                prPair.RelativeRotation += mFrameData[i].RelativeRotation;
            }

            camera.transform.position += prPair.RelativePosition;
            camera.transform.eulerAngles += prPair.RelativeRotation;
        }

        void OnChangedValue(int trackIndex, float value)
        {
            if (trackIndex == TrackConst.PX)
            {
                var tmp = mFrameData[mCurrentFrame].RelativePosition;
                tmp.x = value;
                mFrameData[mCurrentFrame].RelativePosition = tmp;
            }
            else if (trackIndex == TrackConst.PY)
            {
                var tmp = mFrameData[mCurrentFrame].RelativePosition;
                tmp.y = value;
                mFrameData[mCurrentFrame].RelativePosition = tmp;
            }
            else if (trackIndex == TrackConst.PZ)
            {
                var tmp = mFrameData[mCurrentFrame].RelativePosition;
                tmp.z = value;
                mFrameData[mCurrentFrame].RelativePosition = tmp;
            }

            else if (trackIndex == TrackConst.RX)
            {
                var tmp = mFrameData[mCurrentFrame].RelativeRotation;
                tmp.x = value;
                mFrameData[mCurrentFrame].RelativeRotation = tmp;
            }
            else if (trackIndex == TrackConst.RY)
            {
                var tmp = mFrameData[mCurrentFrame].RelativeRotation;
                tmp.y = value;
                mFrameData[mCurrentFrame].RelativeRotation = tmp;
            }
            else if (trackIndex == TrackConst.RZ)
            {
                var tmp = mFrameData[mCurrentFrame].RelativeRotation;
                tmp.z = value;
                mFrameData[mCurrentFrame].RelativeRotation = tmp;
            }

            UpdateTimeline();
            UpdatePreview();
        }

        void OnToPreviousFrameBtnClicked()
        {
            mCurrentFrame = Mathf.Max(0, --mCurrentFrame);

            UpdatePreview();
            UpdateTimeline();
        }

        void OnToNextFrameBtnClicked()
        {
            mCurrentFrame = Mathf.Min(mEndFrame - 1, ++mCurrentFrame);

            UpdatePreview();
            UpdateTimeline();
        }

        void OnToEndFrameBtnClicked()
        {
            mCurrentFrame = mEndFrame - 1;

            UpdatePreview();
            UpdateTimeline();
        }

        void OnToBeginFrameBtnClicked()
        {
            mCurrentFrame = 0;

            UpdatePreview();
            UpdateTimeline();
        }

        void OnPlayBtnClicked(bool toggle)
        {
            mIsPlaying = toggle;

            if (!mIsPlaying)
                mCacheTime = null;

            UpdatePreview();
            UpdateTimeline();
        }

        void OnChangedFPS(float newFps)
        {
            mFPS = newFps;

            UpdatePreview();
        }

        void OnChangeFrame(int newFrame)
        {
            mCurrentFrame = newFrame;

            UpdateTimeline();
        }

        void OnChangeEndFrame(int newEndFrame)
        {
            if (newEndFrame < 1) return;
            if (newEndFrame > 10000) return;

            mEndFrame = newEndFrame;
            mCurrentFrame = 0;

            if (mFrameData.Length != mEndFrame)
                mFrameData = new PRPair[mEndFrame];

            UpdateTimeline();
        }

        void OnLoopPreviewToggleChanged(bool newValue)
        {
            mIsLoopPreview = newValue;

            UpdateCommon();
        }

        void OnNewBtnClicked()
        {
            InitCore();

            View.ResetCameraBeginInfo();

            UpdatePreview();
            UpdateTimeline();
        }

        void OnSaveBtnClicked()
        {
            var path = "";

            if (!string.IsNullOrEmpty(mLastPath))
            {
                if (EditorUtility.DisplayDialog("Save", "is override to current file?", "ok", "Cancel"))
                {
                    path = mLastPath;
                }
            }

            path = string.IsNullOrEmpty(path)
                ? EditorUtility.SaveFilePanelInProject("Save", "WiggleData", "xml", "")
                : path;

            if (!string.IsNullOrEmpty(path))
            {
                HyperWiggleUtil.ConvertPRPair(mFrameData);

                var po = new HyperWigglePO();
                po.Data = mFrameData;
                po.FPS = mFPS;
                po.EndFrame = mEndFrame;

                var xmlStr = HyperWiggleUtil.XmlSerialize(po);
                HyperWiggleUtil.WriteAllText(path, xmlStr);
                AssetDatabase.Refresh();
            }
        }

        void OnLoadBtnClicked()
        {
            mLastPath = EditorUtility.OpenFilePanel("Load", Application.dataPath, "xml");

            if (!string.IsNullOrEmpty(mLastPath))
            {
                var xmlStr = File.ReadAllText(mLastPath);
                Load(xmlStr);
            }
        }

        void OnQuickLoadBtnClicked(UnityEngine.Object dragItem)
        {
            var ext = System.IO.Path.GetExtension(AssetDatabase.GetAssetPath(dragItem));

            if (ext == ".xml")
            {
                mLastPath = AssetDatabase.GetAssetPath(dragItem);

                var textAsset = dragItem as TextAsset;

                Load(textAsset.text);
            }
        }

        void OnCustomButtonClicked(int customButtonIndex)
        {
            mPluginsArray[customButtonIndex].Execute(this);
        }
    }
}
